package borderPatrol.Data;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.Stack;

import borderPatrol.misc.Cloner;
import borderPatrol.misc.History;
import borderPatrol.misc.Judge;

public class Board extends AbstractBoard {

	public final int WIDTH;
	public final int HEIGHT;

	protected final int[] poacherGoal;

	protected int[] poacherPosition;
	protected int[] patrollerPosition;

	protected LinkedList<Move> poacherActions;
	protected LinkedList<Move> patrollerActions;

	protected Move tempPoacherMove;
	protected Move tempPatrollerMove;

	protected String poacherId = "1";
	protected String patrollerId = "2";

	protected Stack<int[][]> history;

	protected LinkedList<History> possiblePoacherHistories;
	protected LinkedList<History> possiblePatrollerHistories;

	protected int round;

	public Board(int width, int height) {

		this.WIDTH = width;
		this.HEIGHT = height;

		poacherPosition = new int[] { -1, -1 };
		patrollerPosition = new int[] { -1, -1 };
		poacherGoal = new int[] { WIDTH, HEIGHT };
		poacherActions = new LinkedList<Move>();
		patrollerActions = new LinkedList<Move>();
		history = new Stack<int[][]>();
		round = 0;

		generatePoacherActions();
		generatePatrollerActions();

		initiatePossibleHistories();
	}

	public Board(int width, int height, Stack<int[][]> history, int[] poacherPosition, int[] patrollerPosition, int round, String id, String poacherId, String patrollerId, LinkedList<History> possiblePoacherHistories, LinkedList<History> possiblePatrollerHistories) {

		this.WIDTH = width;
		this.HEIGHT = height;
		this.history = Cloner.clone(history);
		this.poacherPosition = poacherPosition.clone();
		this.patrollerPosition = patrollerPosition.clone();
		this.round = round;
		this.id = id;
		this.poacherId = poacherId;
		this.patrollerId = patrollerId;
		this.possiblePoacherHistories = Cloner.cloneHistories(possiblePoacherHistories);
		this.possiblePatrollerHistories = Cloner.cloneHistories(possiblePatrollerHistories);

		poacherGoal = new int[] { WIDTH, HEIGHT };

		poacherActions = new LinkedList<Move>();
		patrollerActions = new LinkedList<Move>();

		generatePoacherActions();
		generatePatrollerActions();
	}

	private void initiatePossibleHistories() {

		Stack<int[]> initialPoacherSequence = new Stack<int[]>();
		Stack<int[]> initialPatrollerSequence = new Stack<int[]>();

		initialPatrollerSequence.push(new int[] { -1, -1 });
		initialPoacherSequence.push(new int[] { -1, -1 });

		possiblePatrollerHistories = new LinkedList<History>();
		possiblePoacherHistories = new LinkedList<History>();

		possiblePatrollerHistories.add(new History(initialPatrollerSequence, generateActionsForPlayerAndPosition(new int[] { -1, -1 }, 2), 2));
		possiblePoacherHistories.add(new History(initialPoacherSequence, generateActionsForPlayerAndPosition(new int[] { -1, -1 }, 1), 1));
	}

	protected void generatePatrollerActions() {

		patrollerActions.clear();

		if (isStartingPosition(patrollerPosition)) {
			addMovesOnColumn(patrollerActions, WIDTH - 1);
			return;
		}

		for (int i = -1; i < 2; i += 2) {
			if (isInGrid(patrollerPosition[0] + i, patrollerPosition[1])) {
				patrollerActions.add(new Move(patrollerPosition[0] + i, patrollerPosition[1]));
			}
			if (isInGrid(patrollerPosition[0], patrollerPosition[1] + i)) {
				patrollerActions.add(new Move(patrollerPosition[0], patrollerPosition[1] + i));
			}
		}
	}

	protected void generatePoacherActions() {

		poacherActions.clear();

		if (isStartingPosition(poacherPosition)) {
			addMovesOnColumn(poacherActions, 0);
			return;
		}

		for (int i = -1; i < 2; i += 2) {
			if (isInGrid(poacherPosition[0], poacherPosition[1] + i)) {
				poacherActions.add(new Move(poacherPosition[0], poacherPosition[1] + i));
			}
		}

		if (isInGrid(poacherPosition[0] + 1, poacherPosition[1])) {
			poacherActions.add(new Move(poacherPosition[0] + 1, poacherPosition[1]));
		}

		if (poacherPosition[0] == WIDTH - 1) {
			poacherActions.add(new Move(poacherGoal));
		}

	}

	protected LinkedList<Move> generatePatrollerActionsForPosition(int[] patrollerPosition) {

		LinkedList<Move> patrollerActions = new LinkedList<Move>();

		if (isStartingPosition(patrollerPosition)) {
			addMovesOnColumn(patrollerActions, WIDTH - 1);
			return patrollerActions;
		}

		for (int i = -1; i < 2; i += 2) {
			if (isInGrid(patrollerPosition[0] + i, patrollerPosition[1])) {
				patrollerActions.add(new Move(patrollerPosition[0] + i, patrollerPosition[1]));
			}
			if (isInGrid(patrollerPosition[0], patrollerPosition[1] + i)) {
				patrollerActions.add(new Move(patrollerPosition[0], patrollerPosition[1] + i));
			}
		}

		return patrollerActions;
	}

	protected LinkedList<Move> generatePoacherActionsForPosition(int[] poacherPosition) {

		LinkedList<Move> poacherActions = new LinkedList<Move>();

		if (isStartingPosition(poacherPosition)) {
			addMovesOnColumn(poacherActions, 0);
			return poacherActions;
		}

		for (int i = -1; i < 2; i += 2) {
			if (isInGrid(poacherPosition[0], poacherPosition[1] + i)) {
				poacherActions.add(new Move(poacherPosition[0], poacherPosition[1] + i));
			}
		}

		if (isInGrid(poacherPosition[0] + 1, poacherPosition[1])) {
			poacherActions.add(new Move(poacherPosition[0] + 1, poacherPosition[1]));
		}

		if (poacherPosition[0] == WIDTH - 1) {
			poacherActions.add(new Move(poacherGoal));
		}

		return poacherActions;

	}

	protected LinkedList<Move> generateActionsForPlayerAndPosition(int[] position, int sign) {

		if (sign == 1) {
			return generatePoacherActionsForPosition(position);
		}

		return generatePatrollerActionsForPosition(position);
	}

	private boolean isInGrid(int x, int y) {
		return x >= 0 && y >= 0 && x < WIDTH && y < HEIGHT;
	}

	protected void addMovesOnColumn(LinkedList<Move> actions, int column) {
		for (int i = 0; i < HEIGHT; i++) {
			actions.add(new Move(column, i));
		}
	}

	private boolean isStartingPosition(int[] position) {
		return position[0] == -1 && position[1] == -1;
	}

	@Override
	public void makeMove(Move move, int sign) {

		if (isValid(move, sign)) {

			if (sign == 1) {
				tempPoacherMove = move;
				return;
			}

			tempPatrollerMove = move;
		}
	}

	private boolean isValid(Move move, int sign) {

		if (sign == 1) {
			return poacherActions.contains(move);
		}

		return patrollerActions.contains(move);
	}

	private void updatePatrollerPosition() {
		patrollerPosition[0] = tempPatrollerMove.x;
		patrollerPosition[1] = tempPatrollerMove.y;
	}

	private void updatePoacherPosition() {
		poacherPosition[0] = tempPoacherMove.x;
		poacherPosition[1] = tempPoacherMove.y;
	}

	@Override
	public boolean endTurn() {

		if (tempPatrollerMove == null || tempPoacherMove == null) {
			return false;
		}

		history.push(new int[][] { poacherPosition.clone(), patrollerPosition.clone() });
		round++;

		updatePoacherPosition();
		updatePatrollerPosition();

		id = updateId();
		poacherId = updatePoacherId();
		patrollerId = updatePatrollerId();

		generatePatrollerActions();
		generatePoacherActions();

		tempPatrollerMove = null;
		tempPoacherMove = null;

		updatePossibleHistories();

		return true;
	}

	private void updatePossibleHistories() {
		updatePoacherHistory();
		updatePatrollerHistory();
	}

	private void updatePoacherHistory() {
		
		Iterator<History> iterator = possiblePoacherHistories.iterator();
		LinkedList<History> newPossibleHistories = new LinkedList<History>();

		while (iterator.hasNext()) {

			History possibleHistory = iterator.next();
			LinkedList<Move> movesForHistory = possibleHistory.possibleMoves;
			
			int[] tempLastPatrollerPosition = new int[]{-5, -5};
			int[] tempLastPoacherPosition = new int[]{-6, -6};
			
			if(possibleHistory.sequence.size() > 1)
				tempLastPoacherPosition = possibleHistory.sequence.get(possibleHistory.sequence.size() - 2);
			
			if(history.size() > 1)
				tempLastPatrollerPosition = history.get(history.size() - 2)[1];

			if (isTerminal(possibleHistory.sequence.peek(), history.peek()[1], tempLastPoacherPosition, tempLastPatrollerPosition) != 0) {
				continue;
			}

			for (Move possibleMove : movesForHistory) {
				possibleHistory.sequence.push(new int[] { possibleMove.x, possibleMove.y });
				newPossibleHistories.add(new History(possibleHistory.sequence, generateActionsForPlayerAndPosition(possibleHistory.sequence.peek(), 1), 1));
				possibleHistory.sequence.pop();
			}
		}

		possiblePoacherHistories = newPossibleHistories;
	}

	private void updatePatrollerHistory() {

		Iterator<History> iterator = possiblePatrollerHistories.iterator();
		LinkedList<History> newPossibleHistories = new LinkedList<History>();

		while (iterator.hasNext()) {

			History possibleHistory = iterator.next();
			LinkedList<Move> movesForHistory = possibleHistory.possibleMoves;
			
			int[] tempLastPatrollerPosition = new int[]{-5, -5};
			int[] tempLastPoacherPosition = new int[]{-6, -6};
			
			if(possibleHistory.sequence.size() > 1)
				tempLastPatrollerPosition = possibleHistory.sequence.get(possibleHistory.sequence.size() - 2);
			
			if(history.size() > 1) {
				tempLastPoacherPosition = history.get(history.size() - 2)[0];
			}

			if (isTerminal(history.peek()[0], possibleHistory.sequence.peek(), tempLastPoacherPosition, tempLastPatrollerPosition) != 0) {
				continue;
			}

			for (Move possibleMove : movesForHistory) {
				possibleHistory.sequence.push(new int[] { possibleMove.x, possibleMove.y });
				newPossibleHistories.add(new History(possibleHistory.sequence, generateActionsForPlayerAndPosition(possibleHistory.sequence.peek(), 2), 2));
				possibleHistory.sequence.pop();
			}
		}

		possiblePatrollerHistories = newPossibleHistories;
	}

	public int isTerminal(int[] poacherPosition, int[] patrollerPosition, int[] lastPoacherPosition, int[] lastPatrollerPosition) {	
		return Judge.getInstance().isTerminal(poacherPosition, patrollerPosition, lastPoacherPosition, lastPatrollerPosition);
	}

	protected void setPossibleHistoriesForPlayer(LinkedList<History> newPossibleHistories, int sign) {
		if (sign == 1) {
			possiblePoacherHistories = Cloner.cloneHistories(newPossibleHistories);
		} else {
			possiblePatrollerHistories = Cloner.cloneHistories(newPossibleHistories);
		}
	}
	
	protected void setPositionForPlayer(int[] position, int sign) {
		if(sign == 1) {
			poacherPosition = position;
		} else {
			patrollerPosition = position;
		}
		
	}

	private String updatePatrollerId() {
		return addMoveToId(patrollerId, tempPatrollerMove);
	}

	private String updatePoacherId() {
		return addMoveToId(poacherId, tempPoacherMove);
	}

	private String updateId() {
		return addMoveToId(addMoveToId(id, tempPoacherMove), tempPatrollerMove);
	}

	protected String addMoveToId(String id, Move move) {

		StringBuilder builder = new StringBuilder();

		builder.append(id);
		builder.append(".");
		builder.append(move);

		return builder.toString();
	}

	@Override
	public void undoLastTurn() {

		int[][] lastState = history.pop();

		round--;

		poacherPosition = lastState[0];
		patrollerPosition = lastState[1];

		removeMoveFromIds();

		generatePatrollerActions();
		generatePoacherActions();
	}

	private void removeMoveFromIds() {
		id = id.substring(0, id.length() - 6);
		poacherId = poacherId.substring(0, poacherId.length() - 3);
		patrollerId = patrollerId.substring(0, patrollerId.length() - 3);
	}

	@Override
	public LinkedList<Move> getAvailableActions() {
		return null;
	}

	public int[] getPosition() {
		return null;
	}

	@Override
	public int isTerminal() {

		if (round > 6) {
			return 2;
		}

		if (history.isEmpty()) {
			return 0;
		}
	
		return isTerminal(poacherPosition, patrollerPosition, history.peek()[0], history.peek()[1]);
	}

	public boolean isPoacherOnFinalPosition() {
		return poacherPosition[0] == poacherGoal[0] && poacherPosition[1] == poacherGoal[1];
	}

	public int[] getPatrollerPosition() {
		return patrollerPosition.clone();
	}

	public int[] getPoacherPosition() {
		return poacherPosition.clone();
	}

	public LinkedList<Move> getPatrollerActions() {
		return patrollerActions;
	}

	public LinkedList<Move> getPoacherActions() {
		return poacherActions;
	}

	public LinkedList<History> getPossiblePoacherHistories() {
		return possiblePoacherHistories;
	}

	public LinkedList<History> getPossiblePatrollerHistories() {
		return possiblePatrollerHistories;
	}

	public LinkedList<History> getPossibleHistoriesForPlayer(int sign) {
		return sign == 1 ? possiblePoacherHistories : possiblePatrollerHistories;
	}

	public LinkedList<int[]> getPossiblePositionsOfOpponent(int sign) {

		if (round == 0) {

			LinkedList<int[]> possiblePositions = new LinkedList<int[]>();

			possiblePositions.add(new int[] { -1, -1 });

			return possiblePositions;
		}

		if (sign == 1) {
			return getPossiblePatrollerPositions();
		}

		return getPossiblePoacherPositions();
	}

	private LinkedList<int[]> getPossiblePoacherPositions() {

		LinkedList<int[]> possiblePositions = new LinkedList<int[]>();

		for (int i = 0; i < Math.min(round, WIDTH - 1); i++) {
			for (int j = 0; j < HEIGHT; j++) {
				if (i != patrollerPosition[0] || j != patrollerPosition[1])
					possiblePositions.add(new int[] { i, j });
			}
		}

		return possiblePositions;
	}

	private LinkedList<int[]> getPossiblePatrollerPositions() {

		LinkedList<int[]> possiblePositions = new LinkedList<int[]>();

		for (int i = WIDTH - 1; i > WIDTH - Math.min(round + 1, WIDTH + 1); i--) {
			for (int j = 0; j < HEIGHT; j++) {
				if (i != poacherPosition[0] || j != poacherPosition[1])
					possiblePositions.add(new int[] { i, j });
			}
		}

		return possiblePositions;
	}

	public LinkedList<Move> getActionsForPlayer(int sign) {

		if (sign == 1) {
			return getPoacherActions();
		}

		return getPatrollerActions();
	}

	public int getRound() {
		return round;
	}

	public Board clone() {
		return new Board(WIDTH, HEIGHT, history, poacherPosition, patrollerPosition, round, id, poacherId, patrollerId,
						 possiblePoacherHistories, possiblePatrollerHistories);
	}

	public Board getPoacherBoard() {
		return new PoacherBoard(WIDTH, HEIGHT, history, poacherPosition, patrollerPosition, round, id, poacherId, patrollerId,
								possiblePoacherHistories, possiblePatrollerHistories);
	}

	public Board getPatrollerBoard() {
		return new PatrollerBoard(WIDTH, HEIGHT, history, poacherPosition, patrollerPosition, round, id, poacherId, patrollerId, 
								  possiblePoacherHistories, possiblePatrollerHistories);
	}

	public SimulationBoard getSimulationBoard() {
		return new SimulationBoard(WIDTH, HEIGHT, history, poacherPosition, patrollerPosition, round, id, poacherId, patrollerId, 
								   possiblePoacherHistories, possiblePatrollerHistories);
	}

	public float getGainForPlayer(int player) {

		if (isTerminal() == 0) {
			return 0;
		}

		return isTerminal() == player ? 1 : -1;
	}
}
